// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Reflection;
using MongoDB.Driver;
using Plossum.CommandLine;
using CIP4.Common;
using CIP4.Simple;

using Aquiles;
using Aquiles.Command;
using Aquiles.Command.System;
using Aquiles.Model;


namespace CIP4.Installer
{
    class InstallerProgram
    {
        public const string Appname = "CIP4.Installer";

        static OptionsType _options;

        static StreamWriter _logFile;

        static DateTime _startTime;

        static void Main()
        {
            _startTime = DateTime.Now;

            _logFile = new StreamWriter(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + string.Format("\\{0}.log", Appname), true);

            Globals.DefaultLogger = Logger;

            AlwaysWriteLog(MajorEnum.Information, ">>>>>> Starting " + Appname);

            if (!CheckArgs())
            {
                Environment.ExitCode = 8000;
                return;
            }
            AlwaysWriteLog(MajorEnum.Information, _options.ToString());

            if (_options.Confirm)
            {
                ShowInformational(_options.ToString());
                ShowInformational("Enter Yes to run with these options, No to exit");
                string response = Console.ReadLine()??string.Empty;
                if (!(response.ToLower() == "y" || response.ToLower() == "yes"))
                {
                    Environment.ExitCode = 1000;
                    return;
                }
            }

            if (_options.Action == OptionsType.ActionEnum.None)
            {
                ShowWarning("Nothing to do");

            }
            else if (_options.Action == OptionsType.ActionEnum.List)
            {
                ListConnections();
            }
            else if (_options.Action == OptionsType.ActionEnum.Delete)
            {
                CIP4ConnectionItemType con = CIP4ConnectionItemType.Wrapper.GetViaExternalID(new ExternalIDType(_options.Name));
                if (con != null)
                {
                    CIP4ConnectionItemType.Wrapper.PurgeItems(new[] { new ExternalIDType(_options.Name) });
                }
                else
                {
                    ShowWarning(string.Format("Cannot delete connection {0} as it cannot be found", _options.Name));
                }
            }
            else if (_options.Action == OptionsType.ActionEnum.Add)
            {
                if (_options.Target == ProviderTypesEnum.None)
                {
                    string availableProviders = string.Empty;
                    List<string> availableProviderNames = new List<string>();
                    foreach (FieldInfo field in typeof(ProviderTypesEnum).GetFields())
                    {
                        if (!field.IsLiteral || field.Name == "none")
                            continue;
                        availableProviders += availableProviders.Seperator(",") + field.Name;
                        availableProviderNames.Add(field.Name);
                    }
                    string providerTypeName = "bogus";
                    while (!availableProviderNames.Contains(providerTypeName))
                    {
                        Console.WriteLine(string.Format("Enter the type of provider connection you want to add - must be one of\n{0}", availableProviders));
                        providerTypeName = Console.ReadLine();
                    }
                    _options.Target = (ProviderTypesEnum)Serializer.SerializerBaseType.GetEnum(providerTypeName, typeof(ProviderTypesEnum), default(ProviderTypesEnum));
                }
                switch (_options.Target)
                {
                    case ProviderTypesEnum.File:
                        ConnectionForFile();
                        break;
                    case ProviderTypesEnum.XmlFile:
                        ConnectionForXmlFile();
                        break;
                    case ProviderTypesEnum.Sql:
                        ConnectionForSql();
                        break;
                    case ProviderTypesEnum.MySql:
                        ConnectionForMySql();
                        break;
                    case ProviderTypesEnum.Dictionary:
                        ConnectionForDictionary();
                        break;
                    case ProviderTypesEnum.Mosso:
                        ConnectionForMosso();
                        break;
                    case ProviderTypesEnum.S3:
                        ConnectionForS3();
                        break;
                    case ProviderTypesEnum.Cassandra:
                        ConnectionForCassandra();
                        break;
                    case ProviderTypesEnum.MongoDB:
                        ConnectionForMongoDb();
                        break;
                    default:
                        ShowError(string.Format("{0} is not a supported connection type", _options.Target));
                        break;
                }
            }

        }

        private static void ListConnections()
        {
            Console.WriteLine("Available connection types and their parameters:");
            Console.WriteLine("================================================");
            Type conType = typeof(CIP4ConnectionAttribute);
            foreach (Type subCon in conType.Assembly.GetTypes())
            {
                if (subCon.BaseType != conType)
                    continue;
                Console.WriteLine(TruncateConnectionClassName(subCon.Name) + ":");
                foreach (PropertyInfo prop in subCon.GetProperties())
                {
                    if (prop.CanWrite)
                    {
                        Console.WriteLine(string.Format("\t{0}:{1}", prop.Name, prop.PropertyType.Name));
                    }
                }
            }
            bool gotOne = false;
            foreach (CIP4ConnectionItemType connectionItem in CIP4ConnectionItemType.Wrapper.GetItems(null))
            {
                if (!gotOne)
                    Console.WriteLine("Installed Connections\n=====================\nConnection Type\tConnection Id\tConnection String");
                gotOne = true;
                if (connectionItem.Connection == null)
                    ShowError(string.Format("ConnectionItem is invalid: {0}", connectionItem));
                else
                    Console.WriteLine(string.Format("{0}\t{1}\t{2}", connectionItem.Connection.ProviderKind, connectionItem.Name, connectionItem.Connection.ToString(_options.ShowSensitiveData)));
            }
            if (!gotOne)
            {
                Console.WriteLine("There are no installed connections - use /action add to add one");
            }
        }

        private static void ConnectionForFile()
        {
            string directoryPath = GetValue("BaseDirectoryPath", "A directory will be created in this directory for each of the collections handled by the DataProvider");

            if (!Directory.Exists(directoryPath))
            {
                if (_options.Confirm)
                {
                    ShowWarning(string.Format("the given BaseDirectoryPath does not exist - do you want to create it? Enter 'yes' to create {0}", directoryPath));
                    string response = Console.ReadLine()??string.Empty;
                    if (response.ToLower() != "yes")
                        return;
                }
                else
                {
                    ShowWarning(string.Format("the given BaseDirectoryPath does not exist or is not a directory. Will now attempt to create it: {0}", directoryPath));
                }
                Directory.CreateDirectory(directoryPath);
            }

            CIP4FileConnectionAttribute connection = new CIP4FileConnectionAttribute
            {
                BaseDirectoryPath = directoryPath
            };
            CIP4ConnectionItemType.Wrapper.SaveItems(
                new List<CIP4ConnectionItemType> 
                {
                    new CIP4ConnectionItemType
                    {
                        Name = GetName("File:" + directoryPath),
                        Connection = connection
                    }
                });
            ShowInformational(string.Format("Created connection for {0}", _options.Target));
        }

        private static void ConnectionForXmlFile()
        {
            string fileName = GetValue("XmlFileName", "There will be an xml element in this file for each of the collections handled by the DataProvider");

            if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(Path.GetFileName(fileName)))
            {
                ShowError(string.Format("The given file name does not seem to be a file: {0}", fileName));
                return;
            }

            if (!Directory.Exists(Path.GetDirectoryName(fileName)))
            {
                if (_options.Confirm)
                {
                    ShowWarning(string.Format("the given directory (i.e. the directory for the file name you supplied) does not exist - do you want to create it? Enter 'yes' to create {0}", Path.GetDirectoryName(fileName)));
                    string response = Console.ReadLine()??string.Empty;
                    if (response.ToLower() != "yes")
                        return;
                }
                else
                {
                    ShowWarning(string.Format("the directory for the given XmlFileName does not exist. Will now attempt to create it: {0}", Path.GetDirectoryName(fileName)));
                }
                Directory.CreateDirectory(Path.GetDirectoryName(fileName));
            }

            CIP4XmlFileConnectionAttribute connection = new CIP4XmlFileConnectionAttribute 
            {
                XmlFileName = fileName
            };
            CIP4ConnectionItemType.Wrapper.SaveItems(
                new List<CIP4ConnectionItemType> 
                {
                    new CIP4ConnectionItemType
                    {
                        Name = GetName("XmlFile:" + fileName),
                        Connection = connection
                    }
                });
            ShowInformational(string.Format("Created connection for {0}", _options.Target));
        }

        private static void ConnectionForDictionary()
        {
            string directoryPath = GetValue("BaseDirectoryPath", "A dictionary will be serialized to a file in this directory for each of the collections handled by the DataProvider");

            if (!Directory.Exists(directoryPath))
            {
                if (_options.Confirm)
                {
                    ShowWarning(string.Format("the given BaseDirectoryPath does not exist - do you want to create it? Enter 'yes' to create {0}", directoryPath));
                    string response = Console.ReadLine()??string.Empty;
                    if (response.ToLower() != "yes")
                        return;
                }
                else
                {
                    ShowWarning(string.Format("the given BaseDirectoryPath does not exist or is not a directory. Will now attempt to create it: {0}", directoryPath));
                }
                Directory.CreateDirectory(directoryPath);
            }

            CIP4DictionaryConnectionAttribute connection = new CIP4DictionaryConnectionAttribute
            {
                BaseDirectoryPath = directoryPath
            };
            CIP4ConnectionItemType.Wrapper.SaveItems(
                new List<CIP4ConnectionItemType> 
                {
                    new CIP4ConnectionItemType
                    {
                        Name = GetName("Dictionary:" + directoryPath),
                        Connection = connection
                    }
                });
            ShowInformational(string.Format("Created connection for {0}", _options.Target));
        }

        private static void ConnectionForS3()
        {
            string accessKey = GetValue("AccessKey", "This is the public access key provided by Amazon - you must have an S3 account in order to get one");
            string secretKey = GetSecretValue("SecretKey");

            CIP4S3ConnectionAttribute connection = new CIP4S3ConnectionAttribute
            {
                AccessKey = accessKey,
                SecretKey = secretKey
            };
            CIP4ConnectionItemType.Wrapper.SaveItems(
                new List<CIP4ConnectionItemType> 
                {
                    new CIP4ConnectionItemType
                    {
                        Name = GetName("S3:" + accessKey),
                        Connection = connection
                    }
                });
            ShowInformational(string.Format("Created connection for {0}", _options.Target));
        }

        private static void ConnectionForMosso()
        {
            string userName = GetValue("UserName", "This is the public username providfed by Mosso");
            string accessKey = GetSecretValue("AccessKey");

            CIP4MossoConnectionAttribute connection = new CIP4MossoConnectionAttribute
            {
                UserName = userName,
                AccessKey = accessKey,
            };
            CIP4ConnectionItemType.Wrapper.SaveItems(
                new List<CIP4ConnectionItemType> 
                {
                    new CIP4ConnectionItemType
                    {
                        Name = GetName("Mosso:" + accessKey),
                        Connection = connection
                    }
                });
            ShowInformational(string.Format("Created connection for {0}", _options.Target));
        }

        private static void ConnectionForCassandra()
        {
            string cassandraHome = Environment.GetEnvironmentVariable("CASSANDRA_HOME", EnvironmentVariableTarget.Machine);
            if (string.IsNullOrEmpty(cassandraHome) || !Directory.Exists(cassandraHome) || !File.Exists(Path.Combine(cassandraHome, @"bin\cassandra")))
            {
                if (_options.Confirm)
                {
                    ShowInformational("Cassandra does not seem to be installed (there is no CASSANDRA_HOME environment variable) do you want to install it? - enter 'yes' to install");
                    string response = Console.ReadLine()??string.Empty;
                    if (response.ToLower() != "yes")
                        return;
                }
                else
                {
                    ShowError("Cassandra does not seem to be installed - attempting install now");
                    ShowWarning("Cassandra can be installed from the cassandra apache download site\n " +
                                "http://cassandra.apache.org/download/ you must install 7.0 or later\n" +
                                "Install Java 6\n " +
                                "Download Cassandra extract to a directory (avoid spaces in the directory path)\n" +
                                "set JAVA_HOME To the directory where you installed java (i.e. the directory that contains the bin directory) \n" +
                                "set CASSANDRA_HOME to the directory you extracted cassandra to - again the directory that cntains the bin directory\n" +
                                "At a command prompt run %CASSANDRA_HOME%\\bin\\cassandra.bat");
                    return;
                }

            }

            string keySpace = GetValue("KeySpace", "This is the KeySpace in which the columnFamily will be allocated - keysapce is roughly equivalent to a database");

            string columnFamily = GetValue("ColumnFamily", "This is the columnfamily that will be used to store the CIPl items - all items for this DataProvider will go in the same ColumnFamily");

            if (!CreateColumnFamilyInKeySpace(keySpace, columnFamily))
            {
                ShowError(string.Format("Unable to create columnfamily {0} in keyspace {1}", columnFamily, keySpace));
                return;
            }

            CIP4CassandraConnectionAttribute connection = new CIP4CassandraConnectionAttribute
            {
                Keyspace = keySpace,
                ColumnFamily = columnFamily
            };

            CIP4ConnectionItemType.Wrapper.SaveItems(
                new List<CIP4ConnectionItemType> 
                {
                    new CIP4ConnectionItemType
                    {
                        Name = GetName("Cassandra:" + columnFamily),
                        Connection = connection
                    }
                });

            //ShowInformational(string.Format("Created connection for {0}", options.Target));
        }

        private static void ConnectionForMongoDb()
        {
            string server = GetValueOrDefault("Server", "localhost");
            int port;
            if (!int.TryParse(GetValueOrDefault("Port", "27017"), out port))
            {
                while (port == 0)
                {
                    Console.WriteLine("You must provide an integer - if you provide 0, it will use the default value which is 27017");
                    int.TryParse(GetValue("Port", "The port that will be used to get to Mongo - enter zero to get the default which is 27017"), out port);
                }
            }
            string database = GetValue("Database", "This is the MongoDatabase where the collections will live");

            CIP4MongoDBConnectionAttribute connection = new CIP4MongoDBConnectionAttribute
            {
                Database = database,
                Port = port,
                Server = server
            };

            
            try
            {
                MongoServer mongo = MongoServer.Create(string.Format("mongodb://{0}:{1}", connection.Server, connection.Port));
                if (!mongo.DatabaseExists(connection.Database))
                {
                    ShowError(string.Format("Unable to connect to mongo at {0}:{1} - if it's not installed try installing from http://www.mongodb.org/downloads", server, port));
                    return;
                }
            }
            catch (Exception ex)
            {
                ShowError(string.Format("Exception - Unable to connect to MongoDB at {0}:{1}: {2} ", server, port, ex));
                return;
            }

            CIP4ConnectionItemType.Wrapper.SaveItems(
                new List<CIP4ConnectionItemType> 
                {
                    new CIP4ConnectionItemType
                    {
                        Name = GetName("MongoDB:" + server + "." + database),
                        Connection = connection
                    }
                });

            ShowInformational(string.Format("Created connection for {0}", _options.Target));

        }

        private static void ConnectionForSql()
        {
            string databaseName = GetValue("Database", "This is the sql database name - a new database with this name will be created");
            string host = GetValue("Host", "This is the host where the database is to be found");
            string location = GetValue("Location", @"This is the physical location for the database files, usually something like C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data");
            SetUpSqlDataProviderType ssit = new SetUpSqlDataProviderType(databaseName, host, location, false, Logger);
            if (ssit.ErrorCount == 0)
            {
                CIP4SqlConnectionAttribute connection = new CIP4SqlConnectionAttribute
                {
                    Database = databaseName,
                    Host = host
                };
                CIP4ConnectionItemType.Wrapper.SaveItems(
                    new List<CIP4ConnectionItemType> 
                    {
                        new CIP4ConnectionItemType
                        {
                            Name = GetName("Sql:" + databaseName + " on " + host),
                            Connection = connection
                        }
                    });
            }
        }

        private static void ConnectionForMySql()
        {
            string databaseName = GetValue("Database", "This is the sql database name - a new database with this name will be created");
            string host = GetValue("Host", "This is the host where the database is to be found");
            string userId = GetValue("UserId", "This is the userId that will be used in logging on to the database - root might work");
            string password = GetSecretValue("Password");

            string mysqlexe = FindMySql();

            if (string.IsNullOrEmpty(mysqlexe))
            {
                ShowError("You must first install mysql - see http://dev.mysql.com/downloads/mysql/");
                return;
            }

            // -u {0} --password={1} --database={2} --host={3} --execute="select * from item limit 1"
            // returns the following if item does not exist
            // ERROR 1146 - table doesn't exist, or ERROR 1049 - database does not exist

            List<string> result =
                RunCommandGetValue(
                    mysqlexe,
                    string.Format("-u {0} --password={1} --database={2} --host={3} --execute=\"select * from item limit 1\" --verbose", userId, password, databaseName, host),
                    string.Format("-u {0} --password=******* --database={1} --host={2} --execute=\"select * from item limit 1\" --verbose", userId, databaseName, host),
                    Logger);

            if (result.Any(t => t.Contains("ERROR 1146") || t.Contains("ERROR 1049")))
            {
                StreamReader rdr = new StreamReader(typeof(InstallerProgram).Assembly.GetManifestResourceStream("CIP4.Installer.CIP4.MySql.Schema.txt"));

                string mySqlText = rdr.ReadToEnd().Replace("cip4data", databaseName);

                rdr.Close();

                RunCmd(Path.Combine(Path.GetDirectoryName(mysqlexe), "mysqladmin.exe"),
                    string.Format("-u {0} --password={1} --host={2} --verbose  create {3}", userId, password, host, databaseName),
                    string.Format("-u {0} --password=****** --host={2} --verbose  create {3}", userId, password, host, databaseName),
                    Logger);

                RunCmd(mysqlexe,
                    string.Format("-u {0} --password={1} --database={2} --host={3} --verbose  --execute=\"{4}\"", userId, password, databaseName, host, mySqlText),
                    string.Format("-u {0} --password=****** --database={1} --host={2} --verbose  --execute=\"{3}\"", userId, databaseName, host, mySqlText),
                    Logger);

            }

            CIP4MySqlConnectionAttribute connection = new CIP4MySqlConnectionAttribute
            {
                Database = databaseName,
                Host = host,
                UserId = userId,
                Password = password
            };
            CIP4ConnectionItemType.Wrapper.SaveItems(
                new List<CIP4ConnectionItemType> 
                {
                    new CIP4ConnectionItemType
                    {
                        Name = GetName("MySql:" + databaseName + " on " + host),
                        Connection = connection
                    }
                });
        }

        #region ListConnection and ConnectionFor... helper functions

        private static string GetName(string defaultName)
        {
            if (!string.IsNullOrEmpty(_options.Name))
                return _options.Name;
            return defaultName;
        }

        private static string TruncateConnectionClassName(string name)
        {
            string result = name;
            if (name.StartsWith("CIP4"))
                result = result.Substring(4);
            if (result.EndsWith("ConnectionAttribute"))
                result = result.Substring(0, result.Length - "ConnectionAttribute".Length);
            return result;
        }

        private static string FindMySql()
        {
            foreach (string dir in Directory.GetDirectories(Globals.ProgramFilesDirectory, "MySql", SearchOption.TopDirectoryOnly))
            {
                foreach (string fileName in Directory.GetFiles(dir, "mysql.exe", SearchOption.AllDirectories))
                    return fileName;
            }
            return string.Empty;
        }

        private static string GetValueOrDefault(string valueName, string defaultValue)
        {
            string value;
            if (!_options.ParameterValues.ContainsKey(valueName.ToLower()))
            {
                value = defaultValue;
            }
            else
            {
                value = _options.ParameterValues[valueName.ToLower()][0];
            }
            return value;
        }

        private static string GetValue(string valueName, string explanation)
        {
            string value;
            if (!_options.ParameterValues.ContainsKey(valueName.ToLower()))
            {
                Console.WriteLine("enter " + valueName + " " + explanation);
                value = Console.ReadLine();
            }
            else
            {
                value = _options.ParameterValues[valueName.ToLower()][0];
            }
            return value;
        }

        private static string GetSecretValue(string valueName)
        {
            string value;
            if (!_options.ParameterValues.ContainsKey(valueName.ToLower()))
            {
                Console.WriteLine("enter " + valueName);
                value = ReadPassword();
            }
            else
            {
                value = _options.ParameterValues[valueName.ToLower()][0];
            }
            return value;
        }

        /// <summary>
        /// Use cmdMessage for anything (like passwords) that you don't want visible on the Console
        /// </summary>
        /// <param name="exe"></param>
        /// <param name="cmd"></param>
        /// <param name="cmdMessage"></param>
        /// <param name="logger"></param>
        /// <returns></returns>
        private static void RunCmd(string exe, string cmd, string cmdMessage, LoggerDelegate logger)
        {

            DataReceivedEventHandler outHandler = delegate(object sender, DataReceivedEventArgs e)
            {
                if (!string.IsNullOrEmpty(e.Data))
                {
                    if (e.Data.Contains(": error "))
                        logger(MajorEnum.Error, MinorEnum.Generic, "\n{0}", new object[] { e.Data });
                    else
                        logger(MajorEnum.Information, MinorEnum.Generic, "\n{0}", new object[] { e.Data });
                }
            };
            DataReceivedEventHandler errHandler = delegate(object sender, DataReceivedEventArgs e)
            {
                if (!string.IsNullOrEmpty(e.Data))
                    logger(MajorEnum.Error, MinorEnum.Generic, "\n{0}", new object[] { e.Data });
            };
            EventHandler exitedHandler = delegate(object sender, EventArgs e)
            {
                logger(MajorEnum.Information, MinorEnum.Generic, "\n{0} exited", new object[] { sender.ToString() });
            };

            if (RunCommand(
                exe,
                cmd,
                Globals.CIP4Bin,
                outHandler,
                errHandler,
                exitedHandler,
                cmdMessage,
                logger))
            {
                logger(MajorEnum.Information, MinorEnum.Generic, "Finished running {0} {1}", new object[] { exe, cmdMessage });
                return;
            }

            logger(MajorEnum.Error, MinorEnum.Generic, "Error running {0} {1}", new object[] { exe, cmdMessage });
            return;

        }

        public static List<string> RunCommandGetValue(string exe, string cmd, string cmdMessage, LoggerDelegate logger)
        {
            List<string> values = new List<string>();
            DataReceivedEventHandler outHandler = delegate(object sender, DataReceivedEventArgs e)
            {
                if (!string.IsNullOrEmpty(e.Data))
                {
                    values.Add(e.Data);
                }
            };
            DataReceivedEventHandler errHandler = delegate(object sender, DataReceivedEventArgs e)
            {
                if (!string.IsNullOrEmpty(e.Data))
                {
                    values.Add(e.Data);
                }
                if (!string.IsNullOrEmpty(e.Data))
                    logger(MajorEnum.Error, MinorEnum.Generic, "\n{0}", new object[] { e.Data });
            };
            EventHandler exitedHandler = delegate(object sender, EventArgs e)
            {
                logger(MajorEnum.Information, MinorEnum.Generic, "\n{0} exited", new object[] { sender.ToString() });
            };

            if (RunCommand(
                exe,
                cmd,
                Globals.CIP4Bin,
                outHandler,
                errHandler,
                exitedHandler,
                cmdMessage,
                logger))
            {
                logger(MajorEnum.Information, MinorEnum.Generic, "Finished running {0} {1}", new object[] { exe, cmdMessage });
            }
            else
            {
                logger(MajorEnum.Error, MinorEnum.Generic, "Error running {0} {1}", new object[] { exe, cmdMessage });
            }

            return values;

        }

        public static bool RunCommand(
            string exe,
            string cmd,
            string workingDirectory,
            DataReceivedEventHandler outData,
            DataReceivedEventHandler errorData,
            EventHandler procExited,
            string cmdMessage,
            LoggerDelegate logger)
        {
            try
            {
                logger(MajorEnum.Trace, MinorEnum.Generic, "workingDirectory {0}\n{1} {2}", new object[] { workingDirectory, exe, cmdMessage });

                ProcessStartInfo processInfo = new ProcessStartInfo
                {
                   Verb = "runas",
                   Arguments = cmd,
                   FileName = exe,
                   WorkingDirectory = workingDirectory,
                   RedirectStandardOutput = true,
                   RedirectStandardError = true,
                   UseShellExecute = false,
                   CreateNoWindow = true
                };
                logger(MajorEnum.Trace, MinorEnum.Generic, "-- {0} started doing {1}", new object[] { exe, cmdMessage });
                Process proc = Process.Start(processInfo);
                proc.OutputDataReceived += outData;
                proc.ErrorDataReceived += errorData;
                proc.Exited += procExited;
                proc.BeginOutputReadLine();
                proc.BeginErrorReadLine();
                int seconds = 0;
                while (!proc.WaitForExit(1000))
                    logger(MajorEnum.Noise, MinorEnum.Generic, "-- waited {0} secs for exe to finished doing {1}", new object[] { ++seconds, cmdMessage });
                proc.CancelOutputRead();
                proc.CancelErrorRead();
                proc.Dispose();
                //Logger(MajorEnum.Information, MinorEnum.Generic, "-- csc.exe finished doing {0}", new object[] { cmd });
            }
            catch (Exception ex)
            {
                logger(MajorEnum.Error, MinorEnum.Generic, "Error running {0} {1}: {2}", new object[] { exe, cmdMessage, ex });
                return false;
            }
            return true;
        }

        private static bool CreateColumnFamilyInKeySpace(string keySpace, string columnFamily)
        {
            AddColumnFamily(keySpace, columnFamily);
            return true;
        }

        private const string ClusterName = "CIP4Cluster";

        private static bool AddKeySpace(string keySpace)
        {
            if (string.IsNullOrEmpty(keySpace))
            {
                ShowError("You must provide a KeySpace name with the /KeySpace argument");
                return false;
            }

            RetrieveKeyspacesCommand retrieveKeyspacesCmd = new RetrieveKeyspacesCommand();
            using (IAquilesConnection connection = AquilesHelper.RetrieveConnection(ClusterName))
            {
                connection.Execute(retrieveKeyspacesCmd);
            }
            if (retrieveKeyspacesCmd.Keyspaces.Any(ks => ks.Name.CompareTo(keySpace) == 0))
            {
                ShowWarning(string.Format("KeySpace {0} already exists", keySpace));
                return true;
            }

            AddKeyspaceCommand addKeyspaceCmd = new AddKeyspaceCommand
            {
                KeyspaceDefinition = new AquilesKeyspace
                {
                    Name = keySpace,
                    ReplicationFactor = 1,
                }
            };

            using (IAquilesConnection connection = AquilesHelper.RetrieveConnection(ClusterName))
            {
                connection.Execute(addKeyspaceCmd);
            }

            using (IAquilesConnection connection = AquilesHelper.RetrieveConnection(ClusterName))
            {
                connection.Execute(retrieveKeyspacesCmd);
            }

            if (retrieveKeyspacesCmd.Keyspaces.Any(ks => ks.Name.CompareTo(keySpace) == 0))
            {
                ShowInformational(string.Format("Added keyspace {0}", keySpace));
                return true;
            }

            ShowError(string.Format("Unable to add keyspace {0}", keySpace));
            return false;

        }

        private static void AddColumnFamily(string keySpace, string columnFamily)
        {
            if (string.IsNullOrEmpty(keySpace))
                ShowError("You must provide a KeySpace name with the /KeySpace argument");
            else if (string.IsNullOrEmpty(columnFamily))
                ShowError("You must provide a ColumnFamily name with the /ColumnFamily argument");
            else
            {
                RetrieveKeyspacesCommand retrieveKeyspacesCmd = new RetrieveKeyspacesCommand();
                using (IAquilesConnection connection = AquilesHelper.RetrieveConnection(ClusterName))
                {
                    connection.Execute(retrieveKeyspacesCmd);
                }
                if (!retrieveKeyspacesCmd.Keyspaces.Any(k => k.Name.CompareTo(keySpace) == 0))
                {
                    if (!AddKeySpace(keySpace))
                        return;
                }

                using (IAquilesConnection connection = AquilesHelper.RetrieveConnection(ClusterName))
                {
                    connection.Execute(retrieveKeyspacesCmd);
                }

                AquilesKeyspace ks = retrieveKeyspacesCmd.Keyspaces.FirstOrDefault(k => k.Name.CompareTo(keySpace) == 0);

                if (ks != default(AquilesKeyspace))
                {
                    if (ks.ColumnFamilies.ContainsKey(columnFamily))
                    {
                        ShowWarning(string.Format("ColumnFamily {0} already exists in KeySpace {1}", columnFamily, keySpace));
                        return;
                    }
                }
                else
                {
                    ShowError(string.Format("Unable to access keyspace {0}", keySpace));
                    return;
                }

                AddColumnFamilyCommand cmd = new AddColumnFamilyCommand
                {
                    ColumnFamilyDefinition = new AquilesColumnFamily
                    {
                        Keyspace = keySpace,
                        Name = columnFamily,
                        Comparator = "UTF8Type",
                        Type = AquilesColumnFamilyType.Standard
                    }
                };

                using (IAquilesConnection connection = AquilesHelper.RetrieveConnection(ClusterName, keySpace))
                {
                    connection.Execute(cmd);
                }

                using (IAquilesConnection connection = AquilesHelper.RetrieveConnection(ClusterName))
                {
                    connection.Execute(retrieveKeyspacesCmd);
                }

                ks = retrieveKeyspacesCmd.Keyspaces.FirstOrDefault(k => k.Name.CompareTo(keySpace) == 0);

                if (ks != default(AquilesKeyspace))
                {
                    if (ks.ColumnFamilies.ContainsKey(columnFamily))
                        ShowInformational(string.Format("Added ColumnFamily {0} in KeySpace {1}", columnFamily, keySpace));
                    else
                        ShowError(string.Format("Unable to add ColumnFamily {0} in KeySpace {1}", columnFamily, keySpace));
                }
                else
                    ShowError(string.Format("Unable to add keyspace {0}", keySpace));

            }
        }

        #endregion

        #region Validation and message handling

        public static string ReadPassword()
        {
            Stack<string> passbits = new Stack<string>();
            //keep reading
            for (ConsoleKeyInfo cki = Console.ReadKey(true); cki.Key != ConsoleKey.Enter; cki = Console.ReadKey(true))
            {
                if (cki.Key == ConsoleKey.Backspace)
                {
                    //rollback the cursor and write a space so it looks backspaced to the user
                    Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                    Console.Write(" ");
                    Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                    passbits.Pop();
                }
                else
                {
                    Console.Write("*");
                    passbits.Push(cki.KeyChar.ToString());
                }
            }
            string[] pass = passbits.ToArray();
            Array.Reverse(pass);
            return string.Join(string.Empty, pass);
        }

        private static LoggerResponseEnum Logger(MajorEnum status, MinorEnum minorStatus, string message, object[] paras)
        {
            if (_options.LoggingLevel < status)
                return LoggerResponseEnum.keepGoing;
            lock (_logFile)
            {
                if (status == MajorEnum.Information)
                    ShowInformational(string.Format(message, paras));
                else if (status == MajorEnum.Warning)
                    ShowWarning(string.Format(message, paras));
                else if (status == MajorEnum.Error)
                    ShowError(string.Format(message, paras));
                else if (status == MajorEnum.Noise)
                    Console.Write(string.Format("\r{0} - {1}",
                        TimeSpanString(DateTime.Now - _startTime),
                        message.Substring(0, Math.Min(Console.WindowWidth - 12, message.Length))));
                else
                    Console.WriteLine(string.Format(message, paras));
                WriteLog(status, string.Format(message, paras));
            }
            return LoggerResponseEnum.keepGoing;
        }

        private static string TimeSpanString(TimeSpan value)
        {
            return string.Format("{0,2:00}:{1,2:00}:{2,2:00}", value.Hours, value.Minutes, value.Seconds);
        }

        private static void ShowError(string message)
        {
            lock (_logFile)
            {
                WriteLog(MajorEnum.Error, message);
                Console.BackgroundColor = ConsoleColor.DarkRed;
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine(message);
                Console.ResetColor();
            }
        }

        private static void ShowWarning(string message)
        {
            lock (_logFile)
            {
                if (_options.LoggingLevel >= MajorEnum.Warning)
                    WriteLog(MajorEnum.Warning, message);
                Console.BackgroundColor = ConsoleColor.DarkYellow;
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine(message);
                Console.ResetColor();
            }
        }

        private static void ShowInformational(string message)
        {
            Console.WriteLine(message);
        }

        private static string _lastMessage;

        private static void WriteLog(MajorEnum level, string message)
        {
            if (string.IsNullOrEmpty(message) || message.Trim().Length == 0 || message.Trim() == _lastMessage)
                return;
            _lastMessage = message.Trim();
            AlwaysWriteLog(level, message);
        }

        private static void AlwaysWriteLog(MajorEnum level, string message)
        {
            _lastMessage = message.Trim();
            _logFile.WriteLine(string.Format("{0} - {1}: {2}", DateTime.Now.ToString("MM:dd hh:mm:ss.fff"), level, message.Trim()));
            _logFile.Flush();
        }

        private static bool CheckArgs()
        {
            bool result = true;
            _options = new OptionsType();

            CommandLineParser parser = new CommandLineParser(_options);
            parser.Parse();

            if (parser.HasErrors)
            {
                Console.BackgroundColor = ConsoleColor.DarkRed;
                Console.ForegroundColor = ConsoleColor.White;
                foreach (ErrorInfo err in parser.Errors)
                    Console.WriteLine(err.ToString());
                Console.ResetColor();
                result = false;
            }
            if (parser.RemainingArguments.Count > 0)
            {
                Console.BackgroundColor = ConsoleColor.DarkRed;
                Console.ForegroundColor = ConsoleColor.White;
                Console.Write("Unrecognized input: ");
                foreach (string s in parser.RemainingArguments)
                    Console.Write(s + " ");
                Console.WriteLine();
                Console.ResetColor();
                result = false;
            }

            Console.WriteLine(parser.UsageInfo.GetHeaderAsString(GetWidth() - 2));

            if (_options.Help)
            {
                Console.WriteLine(parser.UsageInfo.GetOptionsAsString(GetWidth() - 2));
                Console.WriteLine("Defaults:");
                Console.WriteLine(_options.ToString());
                Console.WriteLine(string.Format("Logging to {0}", Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + string.Format("\\{0}.log", Appname)));
                result = false;
            }

            return result;
        }

        private static int GetWidth()
        {
            try
            {
                return Console.WindowWidth;
            }
            catch
            {
                return 72;
            }
        }

        #endregion
    }
}
